package contract.jvm.instructions;

import contract.jvm.Frame;
import contract.jvm.Jvm;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

public class InvokeMethod {

    /**
     * 调用类静态方法
     */
    public static void invokestatic(Jvm jvm) {
        invokeMethod(jvm);
    }

    /**
     * 调用类实例方法
     */
    public static void invokevirtual(Jvm jvm) {
        invokeMethod(jvm);
    }

    /**
     * 调用实例方法；对超类、私有和实例初始化方法调用的特殊处理
     */
    public static void invokespecial(Jvm jvm) {
        invokeMethod(jvm);
    }

    private static void invokeMethod(Jvm jvm) {
        Frame frame = jvm.jvmStack.lastElement();
        MethodInsnNode methodInsnNode = (MethodInsnNode) jvm.pc.currentInsnNode;
        String className = methodInsnNode.owner;
        String methodName = methodInsnNode.name;
        String methodDesc = methodInsnNode.desc;

        jvm.loadClass(className);
        MethodNode methodNode = jvm.methodArea.getMethod(className, methodName, methodDesc);

        Type[] types = Type.getArgumentTypes(methodDesc);
        int size = types.length;
        boolean isStatic = (methodNode.access & Opcodes.ACC_STATIC) != 0;
        if (!isStatic) {
            size++;
        }

        Object[] args = new Object[size];
        for (int i = 0; i < size; i++) {
            Object arg = frame.operandStack.pop();
            args[size - i - 1] = arg;
        }

        AbstractInsnNode currentInsnNode = jvm.pc.currentInsnNode;

        jvm.runMethod(className, methodName, methodDesc, args);

        jvm.pc.currentInsnNode = currentInsnNode;
    }

}
